package com.jwcao.common.scan.object;

import com.esotericsoftware.reflectasm.ConstructorAccess;
import com.esotericsoftware.reflectasm.FieldAccess;
import com.esotericsoftware.reflectasm.MethodAccess;
import com.jwcao.common.utils.ReflectUtil;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.Setter;
import org.apache.commons.lang3.StringUtils;

/**
 * name: ScanClass description: TODO
 *
 * @author caojingwei date: 2023/3/22 10:24
 */
@Getter
@Setter
public class ScanClass<T> {

    private final Class<T> clazz;

//    private final List<Constructor> constructorList;

    private final List<Field> fieldList;

    private final List<Method> methodList;

    private final List<ScanField> scanFieldList;

    private final Map<String, ScanField> scanFieldMap;

//    private final List<Method> methodList;

    private final FieldAccess fieldAccess;

    private final ConstructorAccess constructorAccess;

    private final MethodAccess methodAccess;

    public ScanClass(Class<T> clazz) {
        this.clazz = clazz;

        this.constructorAccess = ConstructorAccess.get(clazz);
        this.fieldAccess = FieldAccess.get(clazz);
        this.methodAccess = MethodAccess.get(clazz);
        fieldList = ReflectUtil.getInheritedDeclaredFields(clazz);
        methodList = ReflectUtil.getInheritedDeclaredMethods(clazz);
        scanFieldList = fieldList.stream().map(f -> new ScanField<T>(f, clazz, fieldAccess, methodAccess))
            .collect(Collectors.toList());
        scanFieldMap = scanFieldList.stream().collect(Collectors.toMap(ScanField::getName, Function.identity()));
    }

    public int fieldIndex(String name) {
        for (int i = 0, size = scanFieldList.size(); i < size; i++) {
            ScanField field = scanFieldList.get(i);
            if (StringUtils.equals(field.getName(), name)) {
                return i;
            }
        }
        //TODO:: 这里考虑报错
        return -1;
    }

    public Object instance() {
        return constructorAccess.newInstance();
    }

    public Object instance(Object... args) {
        try {
            return clazz.getDeclaredConstructor().newInstance(args);
        } catch (InstantiationException e) {
            //TODO:: 异常处理要做
            e.printStackTrace();
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } catch (InvocationTargetException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    public ScanField getField(String name) {
        return scanFieldMap.get(name);
    }

    public Object getFieldValue(Object target, String fieldName) {
        ScanField field = scanFieldMap.get(fieldName);
        if (field == null) {
            return null;
        }
        return field.get(target);
    }

    public Object invokeMethod(Object target, String methodName) {
        return methodAccess.invoke(target, methodName);
    }

    public Object invokeMethod(Object target, String methodName, Object... args) {
        return methodAccess.invoke(target, methodName, args);
    }

    public void invokeVoidMethod(Object target, String methodName, Object... args) {
        methodAccess.invoke(target, methodName, args);
    }

    public void setFieldValue(Object target, String fieldName, Object value) {
        ScanField field = scanFieldMap.get(fieldName);
        if (field != null) {
            field.set(target, value);
        }
    }
}
